首页 / 技术类 / COM / 山寨一下 ATL 的 COM_INTERFACE

山寨一下 ATL 的 COM_INTERFACE

2012-09-03 23:17:00

上一篇我们简单学习了下ATL 的继承链处理。可是,如果要裸写一个含内嵌IE控件的窗口,还是要写一个很长的QueryInterface,以及AddRef和Release,确保引用计数的正确性。于是我们不得不参考ATL的COM_TNTERFACE的处理技巧,来达到一定程度上的易用性。

首先,除了IUnknown以外,其余所有涉及到的接口,均按上一篇的形式,弄成相应的IXXXImpl,这部分代码见: http://xllib.codeplex.com/SourceControl/changeset/view/19617#315460 细节不再赘述。现在关键是IUnknown的处理: 首先,如果对象继承自两个或以上的COM接口,要保证所有的查询IUnknown的地方都会返回同一个IUnknown*。 其次,在同一对象中,无论哪个接口调用AddRef和Release,都修改同一个引用计数。

基于以上两点,各个IXXXImpl不能自己去实现IUnknown的这三个函数,就是return E_NOTIMPL也不行。对于COM接口来说,IUnknown必须进行有意义的实现。

就拿WebBrowser的例子来说,WebBrowser容器至少实现IOleClientSite、IOleInPlaceSite、IOleInPlaceFrame,通常还会实现IDocHostUIHandler、DWebBrowserEvents2,它们的继承关系是:

如果看虚函数表,将会是这样:

接口 来自于 来自于 来自于
IUnknown IOleClientSite
IOleClientSite IOleClientSite
IUnknown IOleWindow IOleInPlaceSite
IOleWindow IOleInPlaceSite
IOleInPlaceSite IOleInPlaceSite
IUnknown IOleWindow IOleInPlaceUIWindow IOleInPlaceFrame
IOleWindow IOleInPlaceUIWindow IOleInPlaceFrame
IOleInPlaceUIWindow IOleInPlaceFrame
IOleInPlaceFrame IOleInPlaceFrame
IUnknown IDocHostUIHandler
IDocHostUIHandler IDocHostUIHandler
IUnknown IDispatch (DWebBrowserEvents2)
IDispatch (DWebBrowserEvents2) IDispatch (DWebBrowserEvents2)

其中出现了5个IUnknown,我们要在最底层对象上一次性的实现了,而不是在中间层实现。 如果我们裸写QueryInterface,就像是上次一样,会是这个样子:

 1HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObject)
 2{
 3    *ppvObject = nullptr;
 4
 5    if (riid == IID_IUnknown)
 6    {
 7        *ppvObject = (IOleClientSite *)this;
 8    }
 9    else if (riid == IID_IOleInPlaceSite)
10    {
11        *ppvObject = (IOleInPlaceSite *)this;
12    }
13    else if (riid == IID_IOleInPlaceUIWindow)
14    {
15        *ppvObject = (IOleInPlaceUIWindow *)this;
16    }
17    else if (riid == IID_IOleInPlaceFrame)
18    { 
19        *ppvObject = (IOleInPlaceFrame *)this;
20    }
21    else if (riid == IID_IDocHostUIHandler)
22    { 
23        *ppvObject = (IDocHostUIHandler *)this;
24    }
25    else if (riid == IID_IDispatch)
26    { 
27        *ppvObject = (IDispatch *)this;
28    }
29    else if (riid == DIID_DWebBrowserEvents2)
30    { 
31        *ppvObject = (DWebBrowserEvents2 *)this;
32    }
33
34    if (*ppvObject == nullptr)
35    {
36        return E_NOINTERFACE;
37    }
38
39    AddRef();
40    return S_OK;
41}

注意高亮的那一句,查询IUnknown接口时,我们指定了IOleClientSite对应的IUnknown,也就是表中最前面的那个IUnknown。

很容易想到的一个做法是:

1#define COM_INTERFACE(i)            \
2                                    \
3        if (riid == __uuidof(i))    \
4        {                           \
5            *ppvObject = (i *)this; \
6            AddRef();               \
7            return S_OK;            \
8        }

然后前后用 BEGIN、END宏定义,拼凑成完整的QueryInterface。可是这没有解决IUnknown的问题,如果查询IUnknown,“(i *)this”会有歧义。这种定义下,代码上无法知道“第一个接口”是啥,无法写出一个可行的this到IUnknown*的转换。

不会了,于是就抄ATL。ATL中是列了一张表,我简化一下,每一个项的结构定义是:

1struct InterfaceEntry
2{
3    const IID *piid;
4    DWORD_PTR dwOffset;
5};

然后定义如下函数:

 1typedef c ComClass;
 2
 3static const InterfaceEntry *GetEntries()
 4{
 5    static const InterfaceEntry entries[] =
 6    {
 7        { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)8) - 8 },
 8        { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)8) - 8 },
 9        // ...
10        { nullptr, 0 }
11    };
12
13    return entries;
14}

其中c是最终那个对象的类,i是每一个要对外暴露的接口。每一项的第二列存了该接口到类的首地址的偏移量。至于那个8,为什么要用8,是不是他们拍脑袋的?用别常数有没有问题呢……谁来告诉我?(在下面的实际实现中我用了sizeof(nullptr),不知道有木有问题。)

为此,我们定义一个额外的辅助类:

 1template <typename T>
 2class ComClass
 3{
 4public:
 5    ComClass() : m_nRefCount(0)
 6    {
 7        InternalAddRef();
 8    }
 9
10    ~ComClass()
11    {
12      
13    }
14
15public:
16    STDMETHODIMP InternalQueryInterface(const InterfaceEntry *pEntries, REFIID riid, LPVOID *ppvObject)
17    {
18        *ppvObject = nullptr;
19        T *pThis = (T *)this;
20
21        IUnknown *pUnknown = (IUnknown *)((INT_PTR)pThis + pEntries->dwOffset);
22
23        if (riid == __uuidof(IUnknown))
24        {
25            *ppvObject = pUnknown;
26            pUnknown->AddRef();
27            return S_OK;
28        }
29
30        for (const InterfaceEntry *pEntry = pEntries; pEntry->piid != nullptr; ++pEntry)
31        {
32            if (riid == *pEntry->piid)
33            {
34                *ppvObject = (IUnknown *)((INT_PTR)pThis + pEntry->dwOffset);
35                pUnknown->AddRef();
36                return S_OK;
37            }
38        }
39
40        return E_NOINTERFACE;
41    }
42
43    ULONG STDMETHODCALLTYPE InternalAddRef()
44    {
45        return (ULONG)InterlockedIncrement(&m_nRefCount);
46    }
47
48    ULONG STDMETHODCALLTYPE InternalRelease()
49    {
50        LONG nRefCount = InterlockedDecrement(&m_nRefCount);
51
52        if (nRefCount <= 0)
53        {
54            delete this;
55        }
56          
57        return (ULONG)nRefCount;
58    }
59
60protected:
61    LONG m_nRefCount;
62};

它来对三个IUnknown的接口作实际的实现,其中InternalQueryInterface完成从Entries里面找到偏移量,然后算出接口地址返回给外界的过程。然后……规定:所有COM类必须继承刚才这个ComClass!

现在可以来定义COM_INTERFACE以及它的BEGIN、END宏了:

 1#define XL_COM_INTERFACE_BEGIN(c)                                                                   \
 2                                                                                                    \
 3        typedef c ComClass;                                                                         \
 4                                                                                                    \
 5        static const InterfaceEntry *GetEntries()                                                   \
 6        {                                                                                           \
 7            static const InterfaceEntry entries[] =                                                 \
 8            {                                                                                       \
 9
10#define XL_COM_INTERFACE(i)                                                                         \
11                                                                                                    \
12                { &__uuidof(i), (DWORD_PTR)((i *)(ComClass *)sizeof(nullptr)) - sizeof(nullptr) },  \
13
14#define XL_COM_INTERFACE_END()                                                                      \
15                                                                                                    \
16                { nullptr, 0 }                                                                      \
17            };                                                                                      \
18                                                                                                    \
19            return entries;                                                                         \
20        }                                                                                           \
21                                                                                                    \
22        STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject)                                 \
23        {                                                                                           \
24            return InternalQueryInterface(GetEntries(), riid, ppvObject);                           \
25        }                                                                                           \
26                                                                                                    \
27        ULONG STDMETHODCALLTYPE AddRef()                                                            \
28        {                                                                                           \
29            return InternalAddRef();                                                                \
30        }                                                                                           \
31                                                                                                    \
32        ULONG STDMETHODCALLTYPE Release()                                                           \
33        {                                                                                           \
34            return InternalRelease();                                                               \
35        }                                                                                           \

其实主要就是在拼凑那张表,其余都是直接带上的。 上面的代码位于:
http://xllib.codeplex.com/SourceControl/changeset/view/19617#315452 http://xllib.codeplex.com/SourceControl/changeset/view/19617#315458

现在来做OleContainer(http://xllib.codeplex.com/SourceControl/changeset/view/19617#315455)。

  1class OleContainerImpl : public IOleClientSiteImpl<>,
  2                            public IOleInPlaceSiteImpl<>,
  3                            public IOleInPlaceFrameImpl<>
  4{
  5public:
  6    OleContainerImpl() : m_hOleParent(nullptr),
  7                            m_pStorage(nullptr),
  8                            m_pOleObj(nullptr),
  9                            m_pInPlaceObj(nullptr),
 10                            m_bInPlaceActived(false)
 11    {
 12        ZeroMemory(&m_rect, sizeof(RECT));
 13
 14        OleInitialize(nullptr);
 15    }
 16
 17    virtual ~OleContainerImpl()
 18    {
 19        DestroyOleObject();
 20
 21        OleUninitialize();
 22    }
 23
 24public:
 25    bool CreateOleObject(const IID &clsid)
 26    {
 27        DestroyOleObject();
 28
 29        HRESULT hr = StgCreateDocfile(nullptr,
 30                                        STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT | STGM_CREATE,
 31                                        0,
 32                                        &m_pStorage);
 33        if (FAILED(hr))
 34        {
 35            return false;
 36        }
 37
 38        hr = OleCreate(clsid, IID_IOleObject, OLERENDER_DRAW, 0, this, m_pStorage, (LPVOID *)&m_pOleObj);
 39
 40        if (FAILED(hr))
 41        {
 42            return false;
 43        }
 44
 45        hr = m_pOleObj->QueryInterface(IID_IOleInPlaceObject, (LPVOID *)&m_pInPlaceObj);
 46
 47        if (FAILED(hr))
 48        {
 49            return false;
 50        }
 51
 52        return true;
 53    }
 54
 55    void DestroyOleObject()
 56    {
 57        if (m_pInPlaceObj != nullptr)
 58        {
 59            m_pInPlaceObj->Release();
 60            m_pInPlaceObj = nullptr;
 61        }
 62
 63        if (m_pOleObj != nullptr)
 64        {
 65            m_pOleObj->Release();
 66            m_pOleObj = nullptr;
 67        }
 68
 69        if (m_pStorage != nullptr)
 70        {
 71            m_pStorage->Release();
 72            m_pStorage = nullptr;
 73        }
 74    }
 75
 76    bool InPlaceActive(HWND hWnd, LPCRECT lpRect = nullptr)
 77    {
 78        if (hWnd == nullptr || m_pOleObj == nullptr)
 79        {
 80            return false;
 81        }
 82
 83        m_hOleParent = hWnd;
 84
 85        if (lpRect != nullptr)
 86        {
 87            CopyMemory(&m_rect, lpRect, sizeof(RECT));
 88        }
 89        else
 90        {
 91            GetClientRect(m_hOleParent, &m_rect);
 92        }
 93
 94        HRESULT hr = m_pOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, nullptr, this, 0, m_hOleParent, &m_rect);
 95
 96        if (FAILED(hr))
 97        {
 98            return false;
 99        }
100
101        return true;
102    }
103
104public:
105    STDMETHOD(GetWindow)(HWND *phwnd)
106    {
107        *phwnd = m_hOleParent;
108        return S_OK;
109    }
110
111    STDMETHOD(CanInPlaceActivate)()
112    {
113        return m_bInPlaceActived ? S_FALSE : S_OK;
114    }
115
116    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame,
117                                IOleInPlaceUIWindow **ppDoc,
118                                LPRECT lprcPosRect,
119                                LPRECT lprcClipRect,
120                                LPOLEINPLACEFRAMEINFO lpFrameInfo)
121    {
122        if (m_hOleParent == nullptr)
123        {
124            return E_NOTIMPL;
125        }
126
127        *ppFrame = (IOleInPlaceFrame*)this;
128        (*ppFrame)->AddRef();
129
130        *ppDoc = NULL;
131
132        CopyMemory(lprcPosRect, &m_rect, sizeof(RECT));
133        CopyMemory(lprcClipRect, &m_rect, sizeof(RECT));
134
135        lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO);
136        lpFrameInfo->fMDIApp = false;
137        lpFrameInfo->hwndFrame = GetParent(m_hOleParent);
138        lpFrameInfo->haccel = nullptr;
139        lpFrameInfo->cAccelEntries = 0;
140
141        return S_OK;
142    }
143      
144protected:
145    HWND               m_hOleParent;
146    IStorage          *m_pStorage;
147    IOleObject        *m_pOleObj;
148    IOleInPlaceObject *m_pInPlaceObj;
149    bool               m_bInPlaceActived;
150    RECT               m_rect;
151};
152
153class OleContainer : public ComClass<OleContainer>,
154                        public OleContainerImpl
155{
156public:
157    OleContainer()
158    {
159      
160    }
161
162    ~OleContainer()
163    {
164        DestroyOleObject();
165    }
166
167public:
168    XL_COM_INTERFACE_BEGIN(OleContainer)
169        XL_COM_INTERFACE(IOleClientSite)
170        XL_COM_INTERFACE(IOleInPlaceSite)
171        XL_COM_INTERFACE(IOleInPlaceFrame)
172    XL_COM_INTERFACE_END()
173};

对于InPlaceActive,之前理解有误,OleCreate的时候,并不需要给出窗口,到InPlaceActive之前的瞬间给出即可。而CanInPlaceActive,之前是根据窗口句柄是否为空来确定S_OK还是S_FALSE,这有错误。快要InPlaceActive了,于是去把m_hWnd设上,InPlaceActive去调用CanInPlaceActive,CanInPlaceActive发现窗口句柄有了,于是拒绝……所以形成了我在《裸写一个含内嵌IE控件的窗口》中的尴尬局面。按照上面的实现,逻辑上就没问题了。

另外,这里拆成两个,是为了让后面的WebBrowser继承OleContainerImpl,而OleContainer自己又可以独立使用。

还有需要注意的是,OleContainer的析构函数里最好主动调用一下父类的资源释放函数,不然,等子类完全析构后再自动调用到父类的析构函数,子类(子类部分)已经完全消亡了,但是QueryInterface之类的本来是定义在子类的,这时候可能会出现纯虚函数调用的错误。下同。

再来做WebBrowser(http://xllib.codeplex.com/SourceControl/changeset/view/19615#315453):

  1class WebBrowserImpl : public OleContainerImpl,
  2                        public IDocHostUIHandlerImpl<>,
  3                        public DWebBrowserEvents2Impl<>
  4{
  5public:
  6    WebBrowserImpl() : m_pWebBrowser(nullptr),
  7                        m_pCPC(nullptr),
  8                        m_pCP(nullptr)
  9    {
 10
 11    }
 12
 13    ~WebBrowserImpl()
 14    {
 15        DestroyWebBrowser();
 16    }
 17
 18public:
 19    bool CreateWebBrowser(HWND hWnd, LPCRECT lpRect = nullptr)
 20    {
 21        DestroyWebBrowser();
 22
 23        if (!CreateOleObject(__uuidof(::WebBrowser)))
 24        {
 25            return false;
 26        }
 27
 28        if (!InPlaceActive(hWnd, lpRect))
 29        {
 30            return false;
 31        }
 32
 33        HRESULT hr = m_pOleObj->QueryInterface(__uuidof(IWebBrowser2), (LPVOID *)&m_pWebBrowser);
 34
 35        if (FAILED(hr))
 36        {
 37            return false;
 38        }
 39
 40        hr = m_pWebBrowser->QueryInterface(__uuidof(IConnectionPointContainer), (LPVOID *)&m_pCPC);
 41
 42        if (FAILED(hr))
 43        {
 44            return false;
 45        }
 46
 47        hr = m_pCPC->FindConnectionPoint(__uuidof(DWebBrowserEvents2), &m_pCP);
 48
 49        if (FAILED(hr))
 50        {
 51            return false;
 52        }
 53
 54        DWORD dwCookie = 0;
 55        hr = m_pCP->Advise((DWebBrowserEvents2 *)this, &dwCookie);
 56
 57        if (FAILED(hr))
 58        {
 59            return false;
 60        }
 61
 62        return true;
 63    }
 64
 65    void DestroyWebBrowser()
 66    {
 67        if (m_pCP != nullptr)
 68        {
 69            m_pCP->Release();
 70            m_pCP = nullptr;
 71        }
 72
 73        if (m_pCPC != nullptr)
 74        {
 75            m_pCPC->Release();
 76            m_pCPC = nullptr;
 77        }
 78
 79        if (m_pWebBrowser != nullptr)
 80        {
 81            m_pWebBrowser->Release();
 82            m_pWebBrowser = nullptr;
 83        }
 84
 85        DestroyOleObject();
 86    }
 87
 88protected:
 89    IWebBrowser2              *m_pWebBrowser;
 90    IConnectionPointContainer *m_pCPC;
 91    IConnectionPoint          *m_pCP;
 92};
 93
 94class WebBrowser : public ComClass<WebBrowser>,
 95                    public WebBrowserImpl
 96{
 97public:
 98    WebBrowser()
 99    {
100
101    }
102
103    ~WebBrowser()
104    {
105        DestroyWebBrowser();
106    }
107
108public:
109    XL_COM_INTERFACE_BEGIN(WebBrowser)
110        XL_COM_INTERFACE(IOleClientSite)
111        XL_COM_INTERFACE(IOleInPlaceSite)
112        XL_COM_INTERFACE(IOleInPlaceFrame)
113        XL_COM_INTERFACE(IDocHostUIHandler)
114        XL_COM_INTERFACE(DWebBrowserEvents2)
115    XL_COM_INTERFACE_END()
116};

这里很普通,没什么要说的。现在就可以跟原来一样使用了:

Main.cpp

 1class WebBrowser : public xl::WebBrowser
 2{
 3public:
 4    void Navigate(LPCTSTR lpUrl)
 5    {
 6        BSTR bstrUrl = SysAllocString(lpUrl);
 7        m_pWebBrowser->Navigate(bstrUrl, nullptr, nullptr, nullptr, nullptr);
 8        SysFreeString(bstrUrl);
 9    }
10};
11
12LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
13{
14    switch (message)
15    {
16    case WM_DESTROY:
17        PostQuitMessage(0);
18        break;
19    default:
20        return DefWindowProc(hWnd, message, wParam, lParam);
21    }
22
23    return 0;
24}
25
26int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,
27                       _In_opt_ HINSTANCE hPrevInstance,
28                       _In_ LPTSTR        lpCmdLine,
29                       _In_ int           nCmdShow)
30{
31    UNREFERENCED_PARAMETER(hPrevInstance);
32    UNREFERENCED_PARAMETER(lpCmdLine);
33
34    const LPCTSTR CLASS_NAME = _T("WebBrowserContainer");
35
36    WNDCLASSEX wcex    = { sizeof(WNDCLASSEX) };
37    wcex.style         = CS_HREDRAW | CS_VREDRAW;
38    wcex.lpfnWndProc   = WndProc;
39    wcex.cbClsExtra    = 0;
40    wcex.cbWndExtra    = 0;
41    wcex.hInstance     = hInstance;
42    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);
43    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
44    wcex.lpszClassName = CLASS_NAME;
45
46    RegisterClassEx(&wcex);
47
48    HWND hWnd = CreateWindow(CLASS_NAME,
49                             _T("WebBrowser Sample"),
50                             WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
51                             CW_USEDEFAULT,
52                             0,
53                             CW_USEDEFAULT,
54                             0,
55                             nullptr,
56                             nullptr,
57                             hInstance,
58                             nullptr);
59
60    if (hWnd == nullptr)
61    {
62        return 0;
63    }
64
65    ShowWindow(hWnd, nCmdShow);
66    UpdateWindow(hWnd);
67
68    WebBrowser wb;
69
70    if (!wb.CreateWebBrowser(hWnd))
71    {
72        return 0;
73    }
74
75    wb.Navigate(_T("http://www.baidu.com/"));
76
77    MSG msg = {};
78
79    while (GetMessage(&msg, nullptr, 0, 0))
80    {
81        if (!TranslateAccelerator(msg.hwnd, nullptr, &msg))
82        {
83            TranslateMessage(&msg);
84            DispatchMessage(&msg);
85        }
86    }
87
88    return (int)msg.wParam;
89}

运行界面:

和原来一样。例子代码见WebBrowserSample2.rarhttp://pan.baidu.com/s/1c0q4skK)。


首发:http://www.cppblog.com/Streamlet/archive/2012/09/03/189321.html



NoteIsSite/0.4